/*
 *  Copyright 2001-2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package ar.uba.fi.posgrado.economy.service.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.log4j.Logger;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import ar.uba.fi.posgrado.academy.model.Body;
import ar.uba.fi.posgrado.academy.model.Teacher;
import ar.uba.fi.posgrado.common.dao.DAOException;
import ar.uba.fi.posgrado.common.exception.MissingArgumentException;
import ar.uba.fi.posgrado.common.exception.PosgradoException;
import ar.uba.fi.posgrado.common.exception.PosgradoRuntimeException;
import ar.uba.fi.posgrado.common.model.User;
import ar.uba.fi.posgrado.common.util.CriterionBuilder;
import ar.uba.fi.posgrado.common.util.ExpressionOperator;
import ar.uba.fi.posgrado.economy.dao.CollectTypeDAO;
import ar.uba.fi.posgrado.economy.dao.PayDAO;
import ar.uba.fi.posgrado.economy.exception.PayRuleException;
import ar.uba.fi.posgrado.economy.model.CollectType;
import ar.uba.fi.posgrado.economy.model.DebtStatus;
import ar.uba.fi.posgrado.economy.model.Honorary;
import ar.uba.fi.posgrado.economy.model.Pay;
import ar.uba.fi.posgrado.economy.model.ProviderAccount;
import ar.uba.fi.posgrado.economy.service.PayService;

/**
 * Servicio concreto de Pagos/Egresos.<br>
 * 
 * @author nicolas.gonzalez
 * @author pablo.tortorella
 * @see PayService
 */
public class PayServiceImpl implements PayService {

	/** Logger */
	private static Logger logger = Logger.getLogger(PayServiceImpl.class);

	private PayDAO payDAO;

	private CollectTypeDAO collectTypeDAO;

	/** IoC */
	public void setPayDAO(PayDAO payDAO) {
		this.payDAO = payDAO;
	}

	/** IoC */
	public void setCollectTypeDAO(CollectTypeDAO collectTypeDAO) {
		this.collectTypeDAO = collectTypeDAO;
	}

	/**
	 * @see ar.uba.fi.posgrado.economy.service.PayService#create(ar.uba.fi.posgrado.economy.model.Pay)
	 */
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
	public void create(Pay pay) throws MissingArgumentException,
			PayRuleException, PosgradoException {
		this.validateCreatePay(pay);
		Honorary honorary = pay.getHonorary();
		Double honoraryValue = honorary.getValue();
		Double payValue = pay.getValue();

		if (honoraryValue.doubleValue() != payValue.doubleValue()) {
			throw new PayRuleException("El valor del honorario a pagar es de: "
					+ honoraryValue + " y el del pago es de: " + payValue
					+ ". Deben ser iguales");
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Registrando un pago/egreso. Pago: " + pay + " ...");
		}
		Teacher teacher = honorary.getTeacher();
		ProviderAccount account = teacher.getAccount();
		account.setTotalDebt(account.getTotalDebt() - payValue);

		honorary.setDebtStatus(DebtStatus.Paid);

		// Los datos estan modificados. Se guarda...
		try {
			this.payDAO.save(pay);
			logger.info("Pago/egreso: " + pay + " registrado con exito");
		} catch (DAOException e) {
			throw new PosgradoException(
					"Excepcion al registrar el pago/egreso: " + pay, e);
		}
	}

	/**
	 * @see ar.uba.fi.posgrado.economy.service.PayService#delete(ar.uba.fi.posgrado.economy.model.Pay)
	 */
	@Transactional(readOnly = true)
	public void delete(Pay pay) throws PosgradoRuntimeException,
			PayRuleException, PosgradoException {
		// No esta permitido eliminar cobros.
		throw new PayRuleException(
				"Un pago/egreso no puede ser eliminado. Operacion no permitida.");
	}

	/**
	 * @see ar.uba.fi.posgrado.economy.service.PayService#getAllCollectTypes()
	 */
	@Transactional(readOnly = true)
	public List<CollectType> getAllCollectTypes()
			throws PosgradoRuntimeException, PosgradoException {
		try {
			List<CollectType> response = this.collectTypeDAO.findAll();
			logger.info("Tipos de Cobros encontrados: " + response.size());
			return response;
		} catch (DAOException e) {
			throw new PosgradoException("Excepcion buscando tipos de cobro", e);
		}
	}

	/**
	 * @see ar.uba.fi.posgrado.economy.service.PayService#getCollectTypeById(java.lang.Long)
	 */
	@Transactional(readOnly = true)
	public CollectType getCollectTypeById(Long collectTypeId)
			throws PosgradoRuntimeException, PosgradoException {
		try {
			CollectType response = (CollectType) this.collectTypeDAO
					.findById(collectTypeId);
			logger.info("Tipo de cobro con Id: " + collectTypeId
					+ ", encontrado con exito");
			return response;
		} catch (DAOException e) {
			throw new PosgradoException(
					"Excepcion buscando el tipo de Cobro con Id: "
							+ collectTypeId, e);
		}
	}

	/**
	 * @see ar.uba.fi.posgrado.economy.service.PayService#getPaysByConditions(ar.uba.fi.posgrado.economy.model.Pay)
	 */
	@Transactional(readOnly = true)
	public List<Pay> getPaysByConditions(Pay pay)
			throws PosgradoRuntimeException, PosgradoException {
		if (pay == null) {
			if (logger.isDebugEnabled()) {
				logger
						.debug("Buscando pagos/egresos sin condiciones a aplicar a los criterios ...");
			}
			return this.getAll();
		}
		List<Pay> response = null;
		try {
			CriterionBuilder builder = new CriterionBuilder();
			if (pay.getCollectType() != null) {
				builder.add("collectType", pay.getCollectType());
			}
			if (pay.getDate() != null) {
				builder.add("date", pay.getDate(),
						ExpressionOperator.LowerEqualsThan);
			}
			if (pay.getHonorary() != null) {
				builder.add("honorary", pay.getHonorary());
			}
			if (pay.getUser() != null) {
				builder.add("user", pay.getUser());
			}

			response = this.payDAO.findByCriterions(builder.toCriterions());

			// Ordena por apellido del docente y fecha
			Collections.sort(response, new Comparator<Pay>() {
				public int compare(Pay a, Pay b) {
					Teacher teacherA = a.getHonorary().getTeacher();
					Teacher teacherB = b.getHonorary().getTeacher();

					int teacherCompare = teacherA.getName().compareTo(
							teacherB.getName());

					if (teacherCompare == 0) {
						// Se usa el segundo criterio = fecha
						return (a.getDate().compareTo(b.getDate()));
					} else {
						return teacherCompare;
					}
				}
			});
			if (logger.isDebugEnabled()) {
				logger.debug("Resultado ordenado por docente y fecha");
			}
		} catch (DAOException e) {
			throw new PosgradoException(
					"Excepcion buscando pagos/egresos con criterios", e);
		}

		return response;
	}

	/**
	 * @see ar.uba.fi.posgrado.economy.service.PayService#update(ar.uba.fi.posgrado.economy.model.Pay)
	 */
	@Transactional(readOnly = true)
	public Pay update(Pay pay) throws PosgradoRuntimeException,
			PayRuleException, PosgradoException {
		// No esta permitido eliminar cobros.
		throw new PayRuleException(
				"Un pago/egreso no puede ser modificado. Operacion no permitida.");
	}

	/**
	 * @see ar.uba.fi.posgrado.common.service.GenericService#getAll()
	 */
	@Transactional(readOnly = true)
	public List<Pay> getAll() throws PosgradoRuntimeException,
			PosgradoException {
		try {
			List<Pay> response = this.payDAO.findAll();
			logger.info("Pagos/Egresos encontrados: " + response.size());
			return response;
		} catch (DAOException e) {
			throw new PosgradoException("Excepcion buscando pagos/egresos", e);
		}
	}

	/**
	 * @see ar.uba.fi.posgrado.common.service.GenericService#getById(java.lang.Long)
	 */
	@Transactional(readOnly = true)
	public Pay getById(Long id) throws PosgradoRuntimeException,
			PosgradoException {
		try {
			Pay response = (Pay) this.payDAO.findById(id);
			logger.info("Pago/egreso con Id: " + id + ", encontrado con exito");
			return response;
		} catch (DAOException e) {
			throw new PosgradoException(
					"Excepcion buscando el pago/egreso con Id: " + id, e);
		}
	}

	/**
	 * Hace las validaciones basicas.<br>
	 * 
	 * @param pay
	 * @throws MissingArgumentException
	 */
	private void validateCreatePay(Pay pay) throws MissingArgumentException {
		if (pay.getCollectType() == null) {
			throw new MissingArgumentException(
					"El tipo de pago no puede ser nulo");
		}
		if (pay.getHonorary() == null) {
			throw new MissingArgumentException(
					"El honorario sobre el que se aplica el pago no "
							+ "pueder ser nulo");
		}
		if (pay.getUser() == null) {
			throw new MissingArgumentException(
					"El usuario que realiza el pago no puede ser nulo");
		}
	}

	public List<Pay> getPaysByTeacher(Teacher teacher)
			throws PosgradoRuntimeException, PosgradoException {
		List<Pay> pays = new ArrayList<Pay>();
		try {
			pays = this.payDAO.findByTeacherId(teacher.getId());
		} catch (DAOException e) {
			throw new PosgradoException("Excepcion buscando los pagos realizados al docente: "
					+ teacher.getName(), e);
		}
		return pays;
	}

	/** 
	 * @see ar.uba.fi.posgrado.common.service.GenericService#filter(java.util.List, ar.uba.fi.posgrado.common.model.User)
	 */
	public List<Pay> filter(List<Pay> list, User user)
			throws PosgradoRuntimeException, PosgradoException {
		if (!user.hasBodyRole()) {
			return list;
		}
		final Body body = user.getBody();
		if (logger.isDebugEnabled()) {
			logger.debug(	"El usuario tiene el rol Ente. Filtrando la informacion exclusiva para el " +
							"ente: " + body);
		}
		
		CollectionUtils.filter(list, new Predicate() {
			public boolean evaluate(Object object) {
				Pay pay = (Pay)object;
				if (pay.getHonorary().getCourse().getBody().equals(body)) {
					return true;
				}
				
				return false;
			}
		});
		
		return list;
	}
	
}
